﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

using ET;
using Marx;
using Server;
using Marx.Utils;

namespace Demo
{
    [ObjectSystem]
    public class PlayerAwakeSystem : AwakeSystem<Player, string, long>
    {
        public override void Awake(Player self, string account, long accountId)
        {
            self.Awake(account, accountId);
        }
    }

    [ObjectSystem]
    public class PlayerUpdateSystem : UpdateSystem<Player>
    {
        public override void Update(Player self)
        {
            self.Update();
        }
    }

    public class PlayerBaseInfo : Entity
    {
        public string Account;
        public int Platform;
        public bool FirstLogin;
        public long CreateTime;
    }

    public sealed class Player : Entity, IAwake<string, long>, IUpdate
    {
        public string Account { get; private set; }
        public long AccountId { get; private set; }
        public bool Ready { get { return loaded_ && !destroyed_; } }
        public PlayerBaseInfo BaseInfo { get; private set; }
        public DateTime CreateTime { get { return TimeUtils.GetDateTime(BaseInfo.CreateTime); } }
        public TimerSystem Timer => timer_;
        public DBComponent DB => db_;
        public DirtyQueue<PlayerModel> DirtyQueue => dirtyQueue_;

        private long client_;
        private List<PlayerInfo> infos_;
        private List<IDatabase> models_;
        private List<ITick> ticks_;

        private TimerSystem timer_;
        private CoroutineLockComponent lock_;
        private DBComponent db_;
        private DirtyQueue<PlayerModel> dirtyQueue_;

        public void Awake(string account, long accountId)
        {
            Account = account;
            AccountId = accountId;
            infos_ = new List<PlayerInfo>();
            models_ = new List<IDatabase>();
            ticks_ = new List<ITick>();
            timer_ = new TimerSystem();

            lock_ = Game.Scene.GetComponent<CoroutineLockComponent>();
            db_ = DBManagerComponent.Instance.GetZoneDB(this.DomainZone());
            dirtyQueue_ = new DirtyQueue<PlayerModel>(db_, ServerConfigration.Instance.DB.DelayTime);
        }

        public void SetAgentActor(long actor)
        {
            client_ = actor;
        }

        public async ETTask LoadFromDB()
        {
            BaseInfo = await db_.Query<PlayerBaseInfo>(Id);
            if (BaseInfo == null)
            {
                BaseInfo = new PlayerBaseInfo()
                {
                    Id = Id,
                    Platform = 0,
                    FirstLogin = true,
                    Account = Account,
                    CreateTime = TimeUtils.CurUnixTimestamp,
                };
            }

            foreach (var model in models_)
            {
                await model.LoadFromDB();
            }

            foreach (var model in models_)
            {
                await model.OnDBLoaded();
            }
        }

        private bool loaded_ = false;
        public async ETTask Checkout()
        {
            foreach (var info in infos_)
            {
                await info.Checkout();
            }

            foreach (var info in infos_)
            {
                info.OnInitialize();
            }

            loaded_ = true;
        }

        public async ETTask SaveBaseInfo()
        {
            await db_.Save(BaseInfo);
        }

        public void SendMessageToClient(IClientNotify message)
        {
            if (client_ != 0)
            {
                MessageHelper.SendActor(client_, message);
            }
        }

        private List<ItemChangeProto> changes_ = new List<ItemChangeProto>();
        public void AddItemChangeProto(ItemChangeProto proto)
        {
            changes_.Add(proto);
        }

        public List<ItemChangeProto> GetAndClearItemChanges()
        {
            var result = changes_;
            changes_ = new List<ItemChangeProto>();
            return result;
        }

        public void SendItemChangeNotify()
        {
            if (changes_.Count > 0 && Ready)
            {
                var ntf = new ItemChangeNotify();
                ntf.Changes.AddRange(GetAndClearItemChanges());
                SendMessageToClient(ntf);
            }
        }

        public void Update()
        {
            if (!Ready)
                return;

            foreach (var comp in ticks_)
            {
                comp.Tick();
            }
            SendItemChangeNotify();

            dirtyQueue_.Update();
            timer_.Update();
        }

        public K AddPlayerInfo<K>() where K : PlayerInfo, IAwake, new()
        {
            var component = AddComponent<K>();

            var info = component as PlayerInfo;
            if (info != null)
            {
                info.Id = Id;
                info.OnAwake();
                infos_.Add(info);
            }

            var model = component as IDatabase;
            if (model != null)
            {
                models_.Add(model);
            }

            var comp = component as ITick;
            if (comp != null)
            {
                ticks_.Add(comp);
            }
            return component;
        }

        private bool destroyed_ = false;
        public async ETTask Destroy()
        {
            try
            {
                if (destroyed_)
                {
                    return;
                }

                using (await lock_.Wait(MarxCoroutineLock.Player, Id))
                {
                    loaded_ = false;
                    destroyed_ = true;
                    timer_.Clear();
                    ticks_.Clear();
                    infos_.Clear();

                    var mailBox = GetComponent<MailBoxComponent>();
                    if (mailBox != null)
                    {
                        mailBox.Dispose();
                    }
                    await this.RemoveLocation();

                    foreach (var info in infos_)
                    {
                        await info.Cleanup();
                    }

                    DirtyQueue.Clear();
                    foreach (var dbmodel in models_)
                    {
                        await dbmodel.Destroy();
                    }
                    models_.Clear();

                    Dispose();
                    Log.Debug("Player[" + Account + "] destroyed");
                }
            }
            catch (Exception e)
            {
                Log.Error(e);
            }
        }
    }
}
